chatsecureios / ChatSecure / Classes / View Controllers / UserProfileViewController.swift @ 9a71f7ed
History | View | Annotate | Download (19.7 KB)
| 1 |
// |
|---|---|
| 2 |
// OMEMODeviceVerificationViewController.swift |
| 3 |
// ChatSecure |
| 4 |
// |
| 5 |
// Created by Chris Ballinger on 10/13/16. |
| 6 |
// Copyright © 2016 Chris Ballinger. All rights reserved. |
| 7 |
// |
| 8 |
|
| 9 |
import UIKit |
| 10 |
import XLForm |
| 11 |
import YapDatabase |
| 12 |
import OTRAssets |
| 13 |
|
| 14 |
open class UserProfileViewController: XLFormViewController {
|
| 15 |
|
| 16 |
@objc open var completionBlock: (()->Void)? |
| 17 |
|
| 18 |
// Crypto Chooser row tags |
| 19 |
open static let DefaultRowTag = "DefaultRowTag" |
| 20 |
open static let PlaintextRowTag = "PlaintextRowTag" |
| 21 |
open static let OTRRowTag = "OTRRowTag" |
| 22 |
open static let OMEMORowTag = "OMEMORowTag" |
| 23 |
open static let ShowAdvancedCryptoSettingsTag = "ShowAdvancedCryptoSettingsTag" |
| 24 |
|
| 25 |
open let accountKey:String |
| 26 |
open var connection: YapDatabaseConnection |
| 27 |
|
| 28 |
lazy var signalCoordinator:OTROMEMOSignalCoordinator? = {
|
| 29 |
var account:OTRAccount? = nil |
| 30 |
self.connection.read { (transaction) in
|
| 31 |
account = OTRAccount.fetchObject(withUniqueID: self.accountKey, transaction: transaction) |
| 32 |
} |
| 33 |
|
| 34 |
guard let acct = account else {
|
| 35 |
return nil |
| 36 |
} |
| 37 |
|
| 38 |
guard let xmpp = OTRProtocolManager.sharedInstance().protocol(for: acct) as? OTRXMPPManager else {
|
| 39 |
return nil |
| 40 |
} |
| 41 |
return xmpp.omemoSignalCoordinator |
| 42 |
}() |
| 43 |
|
| 44 |
@objc public init(accountKey:String, connection: YapDatabaseConnection, form: XLFormDescriptor) {
|
| 45 |
self.accountKey = accountKey |
| 46 |
self.connection = connection |
| 47 |
super.init(nibName: nil, bundle: nil) |
| 48 |
|
| 49 |
self.form = form |
| 50 |
} |
| 51 |
|
| 52 |
required public init!(coder aDecoder: NSCoder!) {
|
| 53 |
fatalError("init(coder:) has not been implemented")
|
| 54 |
} |
| 55 |
|
| 56 |
open override func viewDidLoad() {
|
| 57 |
// gotta register cell before super |
| 58 |
OMEMODeviceFingerprintCell.registerCellClass(OMEMODeviceFingerprintCell.defaultRowDescriptorType()) |
| 59 |
UserInfoProfileCell.registerCellClass(UserInfoProfileCell.defaultRowDescriptorType()) |
| 60 |
|
| 61 |
super.viewDidLoad() |
| 62 |
self.navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .done, target: self, action: #selector(doneButtonPressed(_:))) |
| 63 |
self.tableView.allowsMultipleSelectionDuringEditing = false |
| 64 |
|
| 65 |
// Overriding superclass behaviour. This prevents the red icon on left of cell for deletion. Just want swipe to delete on device/fingerprint. |
| 66 |
self.tableView.setEditing(false, animated: false) |
| 67 |
} |
| 68 |
|
| 69 |
open override func didReceiveMemoryWarning() {
|
| 70 |
super.didReceiveMemoryWarning() |
| 71 |
// Dispose of any resources that can be recreated. |
| 72 |
} |
| 73 |
|
| 74 |
@objc open func doneButtonPressed(_ sender: AnyObject?) {
|
| 75 |
var devicesToSave: [OTROMEMODevice] = [] |
| 76 |
var otrFingerprintsToSave: [OTRFingerprint] = [] |
| 77 |
for (_, value) in form.formValues() {
|
| 78 |
switch value {
|
| 79 |
case let device as OTROMEMODevice: |
| 80 |
devicesToSave.append(device) |
| 81 |
case let fingerprint as OTRFingerprint: |
| 82 |
otrFingerprintsToSave.append(fingerprint) |
| 83 |
default: |
| 84 |
break |
| 85 |
} |
| 86 |
} |
| 87 |
OTRDatabaseManager.sharedInstance().readWriteDatabaseConnection?.asyncReadWrite({ (t: YapDatabaseReadWriteTransaction) in
|
| 88 |
for viewedDevice in devicesToSave {
|
| 89 |
if var device = t.object(forKey: viewedDevice.uniqueId, inCollection: OTROMEMODevice.collection) as? OTROMEMODevice {
|
| 90 |
device = device.copy() as! OTROMEMODevice |
| 91 |
device.trustLevel = viewedDevice.trustLevel |
| 92 |
|
| 93 |
if (device.trustLevel == .trustedUser && device.isExpired()) {
|
| 94 |
device.lastSeenDate = viewedDevice.lastSeenDate |
| 95 |
} |
| 96 |
|
| 97 |
device.save(with: t) |
| 98 |
} |
| 99 |
} |
| 100 |
}) |
| 101 |
|
| 102 |
otrFingerprintsToSave.forEach { (fingerprint) in
|
| 103 |
OTRProtocolManager.sharedInstance().encryptionManager.save(fingerprint) |
| 104 |
} |
| 105 |
if let completion = self.completionBlock {
|
| 106 |
completion() |
| 107 |
} |
| 108 |
dismiss(animated: true, completion: nil) |
| 109 |
} |
| 110 |
|
| 111 |
fileprivate func isAbleToDeleteCellAtIndexPath(_ indexPath:IndexPath) -> Bool {
|
| 112 |
if let rowDescriptor = self.form.formRow(atIndex: indexPath) {
|
| 113 |
|
| 114 |
switch rowDescriptor.value {
|
| 115 |
case let device as OTROMEMODevice: |
| 116 |
if let myBundle = self.signalCoordinator?.fetchMyBundle() {
|
| 117 |
// This is only used to compare so we don't allow delete UI on our device |
| 118 |
let thisDeviceYapKey = OTROMEMODevice.yapKey(withDeviceId: NSNumber(value: myBundle.deviceId as UInt32), parentKey: self.accountKey, parentCollection: OTRAccount.collection) |
| 119 |
if device.uniqueId != thisDeviceYapKey {
|
| 120 |
return true |
| 121 |
} |
| 122 |
} |
| 123 |
case let fingerprint as OTRFingerprint: |
| 124 |
if (fingerprint.accountName != fingerprint.username) {
|
| 125 |
return true |
| 126 |
} |
| 127 |
default: |
| 128 |
break |
| 129 |
} |
| 130 |
} |
| 131 |
return false |
| 132 |
} |
| 133 |
|
| 134 |
fileprivate func performEdit(_ action:UITableViewCellEditingStyle, indexPath:IndexPath) {
|
| 135 |
if ( action == .delete ) {
|
| 136 |
if let rowDescriptor = self.form.formRow(atIndex: indexPath) {
|
| 137 |
rowDescriptor.sectionDescriptor.removeFormRow(rowDescriptor) |
| 138 |
switch rowDescriptor.value {
|
| 139 |
case let device as OTROMEMODevice: |
| 140 |
|
| 141 |
self.signalCoordinator?.removeDevice([device], completion: { (success) in
|
| 142 |
|
| 143 |
}) |
| 144 |
break |
| 145 |
case let fingerprint as OTRFingerprint: |
| 146 |
do {
|
| 147 |
try OTRProtocolManager.sharedInstance().encryptionManager.otrKit.delete(fingerprint) |
| 148 |
} catch {
|
| 149 |
|
| 150 |
} |
| 151 |
break |
| 152 |
default: |
| 153 |
break |
| 154 |
} |
| 155 |
} |
| 156 |
} |
| 157 |
} |
| 158 |
|
| 159 |
open static func cryptoChooserRows(_ buddy: OTRBuddy, connection: YapDatabaseConnection) -> [XLFormRowDescriptor] {
|
| 160 |
|
| 161 |
let bestAvailableRow = XLFormRowDescriptor(tag: DefaultRowTag, rowType: XLFormRowDescriptorTypeBooleanCheck, title: Best_Available()) |
| 162 |
let plaintextOnlyRow = XLFormRowDescriptor(tag: PlaintextRowTag, rowType: XLFormRowDescriptorTypeBooleanCheck, title: Plaintext_Only()) |
| 163 |
let plaintextOtrRow = XLFormRowDescriptor(tag: PlaintextRowTag, rowType: XLFormRowDescriptorTypeBooleanCheck, title: Plaintext_Opportunistic_OTR()) |
| 164 |
let otrRow = XLFormRowDescriptor(tag: OTRRowTag, rowType: XLFormRowDescriptorTypeBooleanCheck, title: "OTR") |
| 165 |
let omemoRow = XLFormRowDescriptor(tag: OMEMORowTag, rowType: XLFormRowDescriptorTypeBooleanCheck, title: "OMEMO") |
| 166 |
|
| 167 |
var hasDevices = false |
| 168 |
|
| 169 |
connection.read { (transaction: YapDatabaseReadTransaction) in
|
| 170 |
if OTROMEMODevice.allDevices(forParentKey: buddy.uniqueId, collection: type(of: buddy).collection, transaction: transaction).count > 0 {
|
| 171 |
hasDevices = true |
| 172 |
} |
| 173 |
} |
| 174 |
|
| 175 |
if (!hasDevices) {
|
| 176 |
omemoRow.disabled = NSNumber(value: true as Bool) |
| 177 |
} |
| 178 |
|
| 179 |
let trueValue = NSNumber(value: true as Bool) |
| 180 |
switch buddy.preferredSecurity {
|
| 181 |
case .plaintextOnly: |
| 182 |
plaintextOnlyRow.value = trueValue |
| 183 |
break |
| 184 |
case .bestAvailable: |
| 185 |
bestAvailableRow.value = trueValue |
| 186 |
break |
| 187 |
case .OTR: |
| 188 |
otrRow.value = trueValue |
| 189 |
break |
| 190 |
case .OMEMO: |
| 191 |
omemoRow.value = trueValue |
| 192 |
break |
| 193 |
case .omemOandOTR: |
| 194 |
omemoRow.value = trueValue |
| 195 |
break |
| 196 |
case .plaintextWithOTR: |
| 197 |
plaintextOtrRow.value = trueValue |
| 198 |
} |
| 199 |
|
| 200 |
let formRows = [bestAvailableRow, plaintextOnlyRow, plaintextOtrRow, otrRow, omemoRow] |
| 201 |
|
| 202 |
var currentRow: XLFormRowDescriptor? = nil |
| 203 |
var rowsToDeselect: NSMutableSet = NSMutableSet() |
| 204 |
let onChangeBlock = { (oldValue: Any?, newValue: Any?, rowDescriptor: XLFormRowDescriptor) in
|
| 205 |
// Prevent infinite loops |
| 206 |
// Allow deselection |
| 207 |
if rowsToDeselect.count > 0 {
|
| 208 |
rowsToDeselect.remove(rowDescriptor) |
| 209 |
return |
| 210 |
} |
| 211 |
if currentRow != nil {
|
| 212 |
return |
| 213 |
} |
| 214 |
currentRow = rowDescriptor |
| 215 |
|
| 216 |
// Don't allow user to unselect a true value |
| 217 |
if (newValue as AnyObject?)?.boolValue == false {
|
| 218 |
rowDescriptor.value = NSNumber(value: true as Bool) |
| 219 |
currentRow = nil |
| 220 |
return |
| 221 |
} |
| 222 |
|
| 223 |
// Deselect other rows |
| 224 |
rowsToDeselect = NSMutableSet(array: formRows.filter({ $0 != rowDescriptor }))
|
| 225 |
for row in rowsToDeselect {
|
| 226 |
guard let row = row as? XLFormRowDescriptor else {
|
| 227 |
continue |
| 228 |
} |
| 229 |
let newValue = NSNumber(value: false as Bool) |
| 230 |
row.value = newValue |
| 231 |
// Wow that's janky |
| 232 |
(row.sectionDescriptor.formDescriptor.delegate as! XLFormViewControllerDelegate).reloadFormRow!(row) |
| 233 |
} |
| 234 |
|
| 235 |
var preferredSecurity: OTRSessionSecurity = .bestAvailable |
| 236 |
if (plaintextOnlyRow.value as AnyObject?)?.boolValue == true {
|
| 237 |
preferredSecurity = .plaintextOnly |
| 238 |
} else if (otrRow.value as AnyObject?)?.boolValue == true {
|
| 239 |
preferredSecurity = .OTR |
| 240 |
} else if (omemoRow.value as AnyObject?)?.boolValue == true {
|
| 241 |
preferredSecurity = .OMEMO |
| 242 |
} else if (bestAvailableRow.value as AnyObject?)?.boolValue == true {
|
| 243 |
preferredSecurity = .bestAvailable |
| 244 |
} else if (plaintextOtrRow.value as AnyObject?)?.boolValue == true {
|
| 245 |
preferredSecurity = .plaintextWithOTR |
| 246 |
} |
| 247 |
|
| 248 |
OTRDatabaseManager.sharedInstance().readWriteDatabaseConnection?.readWrite({ (transaction: YapDatabaseReadWriteTransaction) in
|
| 249 |
guard var buddy = transaction.object(forKey: buddy.uniqueId, inCollection: type(of: buddy).collection) as? OTRBuddy else {
|
| 250 |
return |
| 251 |
} |
| 252 |
guard let account = buddy.account(with: transaction) else {
|
| 253 |
return |
| 254 |
} |
| 255 |
buddy = buddy.copy() as! OTRBuddy |
| 256 |
buddy.preferredSecurity = preferredSecurity |
| 257 |
buddy.save(with: transaction) |
| 258 |
// Cancel OTR session if plaintext or omemo only |
| 259 |
if (preferredSecurity == .plaintextOnly || preferredSecurity == .OMEMO) {
|
| 260 |
OTRProtocolManager.sharedInstance().encryptionManager.otrKit.disableEncryption(withUsername: buddy.username, accountName: account.username, protocol: account.protocolTypeString()) |
| 261 |
} |
| 262 |
}) |
| 263 |
currentRow = nil |
| 264 |
} |
| 265 |
|
| 266 |
for row in formRows {
|
| 267 |
row.onChangeBlock = onChangeBlock |
| 268 |
} |
| 269 |
|
| 270 |
return formRows |
| 271 |
} |
| 272 |
|
| 273 |
//MARK UITableView Delegate overrides |
| 274 |
|
| 275 |
open override func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
|
| 276 |
if self.isAbleToDeleteCellAtIndexPath(indexPath) {
|
| 277 |
return true |
| 278 |
} |
| 279 |
return false |
| 280 |
} |
| 281 |
|
| 282 |
open override func tableView(_ tableView: UITableView, editingStyleForRowAt indexPath: IndexPath) -> UITableViewCellEditingStyle {
|
| 283 |
if self.isAbleToDeleteCellAtIndexPath(indexPath) {
|
| 284 |
return .delete |
| 285 |
} |
| 286 |
return .none |
| 287 |
} |
| 288 |
|
| 289 |
open override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
|
| 290 |
|
| 291 |
self.performEdit(editingStyle, indexPath: indexPath) |
| 292 |
} |
| 293 |
|
| 294 |
|
| 295 |
@objc open static func profileFormDescriptorForAccount(_ account: OTRAccount, buddies: [OTRBuddy], connection: YapDatabaseConnection) -> XLFormDescriptor {
|
| 296 |
let form = XLFormDescriptor(title: Profile_String()) |
| 297 |
|
| 298 |
let yourProfileSection = XLFormSectionDescriptor.formSection(withTitle: Me_String()) |
| 299 |
let yourProfileRow = XLFormRowDescriptor(tag: account.uniqueId, rowType: UserInfoProfileCell.defaultRowDescriptorType()) |
| 300 |
yourProfileRow.value = account |
| 301 |
yourProfileSection.addFormRow(yourProfileRow) |
| 302 |
|
| 303 |
guard let xmpp = OTRProtocolManager.sharedInstance().protocol(for: account) as? OTRXMPPManager else {
|
| 304 |
return form |
| 305 |
} |
| 306 |
guard let myBundle = xmpp.omemoSignalCoordinator?.fetchMyBundle() else {
|
| 307 |
return form |
| 308 |
} |
| 309 |
let thisDevice = OTROMEMODevice(deviceId: NSNumber(value: myBundle.deviceId as UInt32), trustLevel: .trustedUser, parentKey: account.uniqueId, parentCollection: type(of: account).collection, publicIdentityKeyData: myBundle.identityKey, lastSeenDate: Date()) |
| 310 |
var ourDevices: [OTROMEMODevice] = [] |
| 311 |
connection.read { (transaction: YapDatabaseReadTransaction) in
|
| 312 |
ourDevices = OTROMEMODevice.allDevices(forParentKey: account.uniqueId, collection: type(of: account).collection, transaction: transaction) |
| 313 |
} |
| 314 |
|
| 315 |
|
| 316 |
let ourFilteredDevices = ourDevices.filter({ (device: OTROMEMODevice) -> Bool in
|
| 317 |
return device.uniqueId != thisDevice.uniqueId |
| 318 |
}) |
| 319 |
|
| 320 |
// TODO - Sort ourDevices and theirDevices by lastSeen |
| 321 |
|
| 322 |
let addDevicesToSection: ([OTROMEMODevice], XLFormSectionDescriptor) -> Void = { devices, section in
|
| 323 |
for device in devices {
|
| 324 |
guard let _ = device.publicIdentityKeyData else {
|
| 325 |
continue |
| 326 |
} |
| 327 |
let row = XLFormRowDescriptor(tag: device.uniqueId, rowType: OMEMODeviceFingerprintCell.defaultRowDescriptorType()) |
| 328 |
row.value = device.copy() |
| 329 |
|
| 330 |
// Don't allow editing of your own device |
| 331 |
if device.uniqueId == thisDevice.uniqueId {
|
| 332 |
row.disabled = true |
| 333 |
} |
| 334 |
|
| 335 |
section.addFormRow(row) |
| 336 |
} |
| 337 |
} |
| 338 |
|
| 339 |
let otrKit = OTRProtocolManager.sharedInstance().encryptionManager.otrKit |
| 340 |
let allFingerprints = otrKit.allFingerprints() |
| 341 |
let myFingerprint = otrKit.fingerprint(forAccountName: account.username, protocol: account.protocolTypeString()) |
| 342 |
let addFingerprintsToSection: ([OTRFingerprint], XLFormSectionDescriptor) -> Void = { fingerprints, section in
|
| 343 |
for fingerprint in fingerprints {
|
| 344 |
let row = XLFormRowDescriptor(tag: (fingerprint.fingerprint as NSData).otr_hexString(), rowType: OMEMODeviceFingerprintCell.defaultRowDescriptorType()) |
| 345 |
if let myFingerprint = myFingerprint {
|
| 346 |
if (fingerprint === myFingerprint) {
|
| 347 |
// We implicitly trust ourselves with OTR |
| 348 |
row.disabled = true |
| 349 |
} else {
|
| 350 |
row.disabled = false |
| 351 |
} |
| 352 |
} |
| 353 |
|
| 354 |
row.value = fingerprint |
| 355 |
|
| 356 |
section.addFormRow(row) |
| 357 |
} |
| 358 |
} |
| 359 |
|
| 360 |
var allMyDevices: [OTROMEMODevice] = [] |
| 361 |
allMyDevices.append(thisDevice) |
| 362 |
allMyDevices.append(contentsOf: ourFilteredDevices) |
| 363 |
addDevicesToSection(allMyDevices, yourProfileSection) |
| 364 |
|
| 365 |
var theirSections: [XLFormSectionDescriptor] = [] |
| 366 |
|
| 367 |
if let myFingerprint = myFingerprint {
|
| 368 |
addFingerprintsToSection([myFingerprint], yourProfileSection) |
| 369 |
} |
| 370 |
|
| 371 |
// Add section for each buddy's device |
| 372 |
for buddy in buddies {
|
| 373 |
let theirSection = XLFormSectionDescriptor.formSection(withTitle: buddy.username) |
| 374 |
|
| 375 |
let buddyRow = XLFormRowDescriptor(tag: buddy.uniqueId, rowType: UserInfoProfileCell.defaultRowDescriptorType()) |
| 376 |
buddyRow.value = buddy |
| 377 |
theirSection.addFormRow(buddyRow) |
| 378 |
var theirDevices: [OTROMEMODevice] = [] |
| 379 |
connection.read({ (transaction: YapDatabaseReadTransaction) in
|
| 380 |
theirDevices = OTROMEMODevice.allDevices(forParentKey: buddy.uniqueId, collection: type(of: buddy).collection, transaction: transaction) |
| 381 |
}) |
| 382 |
let theirFingerprints = allFingerprints.filter({ (fingerprint: OTRFingerprint) -> Bool in
|
| 383 |
return fingerprint.username == buddy.username && |
| 384 |
fingerprint.accountName == account.username |
| 385 |
}) |
| 386 |
|
| 387 |
addDevicesToSection(theirDevices, theirSection) |
| 388 |
addFingerprintsToSection(theirFingerprints, theirSection) |
| 389 |
theirSections.append(theirSection) |
| 390 |
} |
| 391 |
|
| 392 |
|
| 393 |
var sectionsToAdd: [XLFormSectionDescriptor] = [] |
| 394 |
sectionsToAdd.append(contentsOf: theirSections) |
| 395 |
|
| 396 |
// cryptoChooserRows is only meaningful for 1:1 conversations at the moment |
| 397 |
if buddies.count == 1 {
|
| 398 |
let buddy = buddies.first! |
| 399 |
let cryptoSection = XLFormSectionDescriptor.formSection(withTitle: Advanced_Encryption_Settings()) |
| 400 |
cryptoSection.footerTitle = Advanced_Crypto_Warning() |
| 401 |
let showAdvancedSwitch = XLFormRowDescriptor.init(tag: self.ShowAdvancedCryptoSettingsTag, rowType: XLFormRowDescriptorTypeBooleanSwitch, title: Show_Advanced_Encryption_Settings()) |
| 402 |
showAdvancedSwitch.value = NSNumber(value: false as Bool) |
| 403 |
let cryptoChooser = cryptoChooserRows(buddy, connection: connection) |
| 404 |
for row in cryptoChooser {
|
| 405 |
cryptoSection.addFormRow(row) |
| 406 |
} |
| 407 |
cryptoSection.hidden = "$\(ShowAdvancedCryptoSettingsTag)==0" |
| 408 |
let buddySection = theirSections.first! |
| 409 |
buddySection.addFormRow(showAdvancedSwitch) |
| 410 |
sectionsToAdd.append(cryptoSection) |
| 411 |
} |
| 412 |
|
| 413 |
sectionsToAdd.append(yourProfileSection) |
| 414 |
|
| 415 |
for section in sectionsToAdd {
|
| 416 |
if section.formRows.count > 0 {
|
| 417 |
form.addFormSection(section) |
| 418 |
} |
| 419 |
} |
| 420 |
|
| 421 |
return form |
| 422 |
} |
| 423 |
|
| 424 |
// MARK: - UITableViewDelegate |
| 425 |
|
| 426 |
open override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
|
| 427 |
super.tableView(tableView, didSelectRowAt: indexPath) |
| 428 |
tableView.deselectRow(at: indexPath, animated: true) |
| 429 |
guard let cell = self.tableView(tableView, cellForRowAt: indexPath) as? OMEMODeviceFingerprintCell else {
|
| 430 |
return |
| 431 |
} |
| 432 |
var fingerprint = "" |
| 433 |
var username = "" |
| 434 |
var cryptoType = "" |
| 435 |
if let device = cell.rowDescriptor.value as? OTROMEMODevice {
|
| 436 |
cryptoType = "OMEMO" |
| 437 |
fingerprint = device.humanReadableFingerprint |
| 438 |
self.connection.read({ (transaction) in
|
| 439 |
if let buddy = transaction.object(forKey: device.parentKey, inCollection: device.parentCollection) as? OTRBuddy {
|
| 440 |
username = buddy.username |
| 441 |
} |
| 442 |
}) |
| 443 |
} |
| 444 |
if let otrFingerprint = cell.rowDescriptor.value as? OTRFingerprint {
|
| 445 |
cryptoType = "OTR" |
| 446 |
fingerprint = (otrFingerprint.fingerprint as NSData).humanReadableFingerprint() |
| 447 |
username = otrFingerprint.username |
| 448 |
} |
| 449 |
if fingerprint.count == 0 || username.count == 0 || cryptoType.count == 0 {
|
| 450 |
return |
| 451 |
} |
| 452 |
let stringToShare = "\(username): \(cryptoType) \(fingerprint)" |
| 453 |
let activityViewController = UIActivityViewController(activityItems: [stringToShare], applicationActivities: nil) |
| 454 |
if let ppc = activityViewController.popoverPresentationController {
|
| 455 |
ppc.sourceView = cell |
| 456 |
ppc.sourceRect = cell.frame |
| 457 |
} |
| 458 |
present(activityViewController, animated: true, completion: nil) |
| 459 |
} |
| 460 |
|
| 461 |
} |